const http = require('http');
const { createCanvas } = require('canvas');
const fs = require('fs');
const htmlparser2 = require('htmlparser2');
const css = require('css');
const main = require('./main');
const network = require('./network');
const gpu = require('./gpu');
const render = require('./render');
const host = 'localhost';
const port = 80;
const loadingLinks = {};
const loadingScript = {};
//2. 浏览器主进程接受请求,会把请求转发给网络进程
main.on('request', function (options) {
  //会把请求转发给网络进程
  network.emit('request', options);
});
//4.主进程 接收到消息后要通知渲染进程开始渲染,提交导航(准备切换页面)消息给渲染进程
main.on('prepareRender', function (response) {
  //主进程发送提交导航的消息给渲染进程
  render.emit('commitNavigation', response);
});

//***********网络进程**********
//3.网络接收请求，发送请求给服务器，读取相应头，告诉主进程准备渲染页面
network.on('request', (options) => {
  //调用http模块发送请求服务
  let request = http.request(options, (response) => {
    let headers = response.headers;
    //告诉主进程请开始渲染页面
    main.emit('prepareRender', response);
  });
  request.end();
});
main.on('drawQuad', () => {
  let drawSteps = gpu.bitMaps.flat();
  const canvas = createCanvas(150, 250);
  const ctx = canvas.getContext('2d');
  eval(drawSteps.join('\r\n'));
  fs.writeFileSync('result.png', canvas.toBuffer('image/png'));
});

//***********渲染进程**********
//5.渲染进程和渲染进程建立管道接受相应提体
render.on('commitNavigation', (response) => {
  const headers = response.headers;
  //获取 响应体的类型 渲染进程
  const contentType = headers['content-type'];
  //说明是html响应
  if (contentType.indexOf('text/html') != -1) {
    const document = { type: 'document', attributes: {}, children: [] };
    const tockenStack = [document];
    const cssRules = [];
    //1.通过渲染进程把html专程字符串转成DOM树
    const parser = new htmlparser2.Parser({
      onopentag(tagName, attributes) {
        //遇到开始标签
        //栈顶就是父节点
        const parent = tockenStack[tockenStack.length - 1];
        const child = {
          type: 'elemnt',
          tagName, //html
          children: [],
          attributes,
        };
        parent.children.push(child);
        tockenStack.push(child);
      },
      ontext(text) {
        if (!/^[\r\s]*$/.test(text)) {
          //文本不需要入栈
          const parent = tockenStack[tockenStack.length - 1];
          const child = {
            type: 'text',
            text,
            tagName: 'text', //html
            children: [],
            attributes: {},
          };
          parent.children.push(child);
        }
      },
      onclosetag(tagName) {
        switch (tagName) {
          case 'style':
            const styleTocken = tockenStack[tockenStack.length - 1];
            const cssAST = css.parse(styleTocken.children[0].text);
            const rules = cssAST.stylesheet.rules;
            cssRules.push(...rules);
          case 'link':
            const linkToken = tockenStack[tockenStack.length - 1];
            const href = linkToken.attributes.href;
            const options = { host, port, path: href };
            const promise = nextwork.fetchResource(options).then(({ body }) => {
              delete loadingLinks[href];
              const cssAst = css.parse(body);
              cssRules.push(...cssAst.stylesheet.rules);
            });
            loadingLinks[href] = promise;
          case 'script':
            const scriptToken = tockenStack[tockenStack.length - 1];
            const src = scriptToken.attributes.src;
            const promises = [...Object.values(loadingLinks),
              ...Object.values(loadingScript)]
            if (src) {
              const options = { host, port, path: href };
              const promise = nextwork
                .fetchResource(options)
                .then(({ body }) => {
                  delete loadingScripts[src];
                  return promise.all(promises).then(() => eval(body))
                });
              loadingScripts[src] = promise;
            } else {
              const script = scriptToken.children[0].text;
              const ts = Date.now();
              const promise = Promise.all([
                ...Object.values(loadingLinks),
                ...Object.values(loadingScript),
              ]).then(() => {
                delete loadingScript[ts];
                eval(body);
              });
              loadingScript[ts] = promise;
            }
          default:
            break;
        }
        //栈顶元素出栈
        tockenStack.pop();
      },
    });
    //一旦接收到部分响应体，直接传递给htmlparse
    response.on('data', (buffer) => {
      parser.write(buffer.toString());
    });
    //传输完毕DOMContentLoaded 加载资源 css图片都完成Load
    response.on('end', () => {
      //需要等待所有的JS都加载执行完毕了，才会进行后续的渲染流程
      Promise.all(Object.values(loadingScripts)).then(() => {
        //计算每个DOM节点的具体的样式继承层叠
        recalculateStyle(cssRules, document);
        //创建只包含一个可见元素的布局树
        const html = document.children[0];
        const body = html.children[1];
        const layoutTree = crerateLayoutTree(body);
        //console.dir(layoutTree,{depth:null});
        //更新布局树，计算每个元素布局信息
        updateLayoutTree(layoutTree);
        console.dir(layoutTree, { depth: null });
        //根据布局树生成分层树
        const layers = [layoutTree]; //默认层
        createLayerTree(layoutTree, layers);
        //根据分层树生成绘制步骤，并复合图层
        const paintSteps = compositeLayers(layers);
        //先切成一个个小的图块
        const tiles = splitTiles(paintSteps);
        raster(tiles);
        //DOM解析完毕
        main.emit('DOMContentLoaded');
        //CSS和图片加载完毕后触发
        main.emit('Load');
      });
    });
  }
});
function rasterThread(tile) {
  //光栅化线程，而是光栅化的工作交给GPU进程来完成，这个叫快速光栅化，或者说GPU光栅化
  gpu.emit('raster', tile);
}
function raster(tile) {
  tile.forEach((tile) => rasterThread(tile));
  //到此位图就生成完毕，通知进程可以显示了
  main.emit('drawQuad');
}
function splitTiles(tiles) {
  tiles.forEach((tile) => rasterThread(tile));
}
//合成图层
function compositeLayers(layers) {
  return layers.map((layer) => paint(layer));
}
function paint(layer, paintSteps = []) {
  const {
    top = 0,
    left = 0,
    color = 'black',
    background = 'white',
    width = 100,
    height = 0,
  } = element;
  if (element.type == 'text') {
    paintSteps.push(`ctx.font = '20px Impact'`); //指定字体
    paintSteps.push(`ctx.strokeStyle = '${color}'`); //指定颜色
    paintSteps.push(
      `ctx.strokeText("${element.text}",${parseFloat(left)},${
        parseFloat(top) + 20
      })`
    );
  } else {
    paintSteps.push(`ctx.fillStyle = '${background}'`);
    paintSteps.push(
      `ctx.fillRect(${parseFloat(left)},${parseFloat(top)},${parseFloat(
        width
      )},${parseFloat(height)})`
    );
  }
  element.children.forEach((child) => paint(child, paintSteps));
  return paintSteps;
}
function createLayerTree(element, layers) {
  //遍历子节点，判断是够要生成新的图层，如果生成，则从当前图层删除
  element.children = element.children.filter(
    (child) => !createNewLayer(child, layers)
  ); //没创建的留下 不创建的留下
  element.children.forEach((child) => createLayerTree(child, layers));
  return layers;
}
function createNewLayer(element, layers) {
  let createNewLayer = false;
  const attributes = element.attributes;
  Object.entries(attributes).forEach((key, value) => {
    if (key === 'style') {
      const attributes = value.split(/;\s*/);
      attributes.forEach((attribute) => {
        const [property, vaule] = attribute.split(/:\s*/);
        if (
          property === 'position' &&
          (value === 'absolute' || value === 'fixed')
        ) {
          //因为这是一个新的层，所以里面的额元素要重新计算一下自己的布局位置
          updateLayoutTree(element);
          layers.push(element);
          createLayerTree = true;
        }
      });
    }
  });
  return createNewLayer;
}
//计算每个布局树上每个元素的布局信息
//element
//top 自己距离自己父节点的顶部的距离
//parentTop
function updateLayoutTree(element, top = 0, parentTop = 0) {
  const computedStyle = element.computedStyle;
  element.layout = {
    top: top + parentTop,
    left: 0,
    with: computedStyle.with,
    height: computedStyle.height,
    color: computedStyle.color,
    background: computedStyle.background,
  };
  let childTop = 0;
  element.children.forEach((child) => {
    updateLayoutTree(child, childTop, element.layout.top);
    childTop += parseFloat(child.computedStyle.height || 0);
  });
}
function crerateLayoutTree(elemnt) {
  elemnt.children = elemnt.children.filter(isShow);
  elemnt.children.forEach(crerateLayoutTree);
  return elemnt;
}
function isShow(elemnt) {
  let show = true; //默认都显示
  if (
    elemnt.tagName === 'head' ||
    elemnt.tagName === 'script' ||
    elemnt.tagName === 'link'
  ) {
    show = false;
  }
  const attributes = elemnt.attributes;
  Object.entries(attributes).forEach(([key, value]) => {
    if (key === 'style') {
      const attributes = value.split(/;\s*/);
      attributes.forEach((attributes) => {
        const [property, value] = attributes.split(/:\s*/);
        if (property === 'display' && value === 'none') {
          show = false;
        }
      });
    }
  });
  return show;
}
function recalculateStyle(cssRules, elemnt, parentStyle = {}) {
  const attributes = elemnt.attributes;
  elemnt.computedStyle = { color: parentStyle.color };
  Object.entries(attributes).forEach(([key, value]) => {
    //应用样式表
    cssRules.forEach((rule) => {
      let selector = rule.selectors[0];
      if (
        (key === 'id' && selector === '#' + value) ||
        (key === 'class' && selector === '.' + value)
      ) {
        rule.declarations.forEach(({ property, value }) => {
          if (property) {
            elemnt.computedStyle[property] = value;
          }
        });
      }
    });
    //行内样式
    if (key === 'style') {
      const attributes = value.split(/;\s*/);
      attributes.forEach((attributes) => {
        const [property, value] = attributes.split(/:\s*/);
        if (property) {
          elemnt.computedStyle[property] = value;
        }
      });
    }
  });
  elemnt.children.forEach((child) =>
    recalculateStyle(cssRules, child, elemnt.computedStyle)
  );
}
gpu.on('raster', (tile) => {
  let bitMap = tile;
  gpu.bitMaps.push(bitMap);
});
//1. 由主进程接受用户输入的URL地址
main.emit('request', {
  host,
  port,
  path: '/index.html',
});
